package cn.kinoko.service.manga.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ZipUtil;
import cn.kinoko.common.base.model.BaseServiceImpl;
import cn.kinoko.common.code.ErrorCodeEnum;
import cn.kinoko.common.utils.CompletableFutureUtil;
import cn.kinoko.common.utils.ExceptionUtil;
import cn.kinoko.common.utils.StrUtil;
import cn.kinoko.mapper.manga.MangaEpMapper;
import cn.kinoko.model.manga.constant.EpCutoutStatus;
import cn.kinoko.model.manga.entity.Manga;
import cn.kinoko.model.manga.entity.MangaEp;
import cn.kinoko.model.manga.entity.MangaEpCooked;
import cn.kinoko.model.manga.entity.MangaEpRaw;
import cn.kinoko.model.manga.req.MangaExportReq;
import cn.kinoko.model.user.entity.UserInfo;
import cn.kinoko.service.common.export.ExportHandler;
import cn.kinoko.service.common.export.ExportTopic;
import cn.kinoko.service.common.export.ExportTopicEnum;
import cn.kinoko.service.common.export.component.ExportManager;
import cn.kinoko.service.common.export.component.ExportTask;
import cn.kinoko.service.common.util.AuthUtil;
import cn.kinoko.service.manga.*;
import cn.kinoko.service.minio.MinioService;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.unit.DataSize;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;

/**
 * @author kinoko
 */
@ExportTopic(ExportTopicEnum.MANGA_ZIP)
@Slf4j
@Service
public class MangaEpServiceImpl extends BaseServiceImpl<MangaEpMapper, MangaEp> implements MangaEpService, ExportHandler {

    @Resource
    private MangaEpRawService mangaEpRawService;
    @Resource
    private MangaEpCookedService mangaEpCookedService;
    @Resource
    private MangaMarkService mangaMarkService;
    @Resource
    private MinioService minioService;
    @Lazy
    @Resource
    private MangaService mangaService;
    @Resource
    private ExportManager exportManager;
    @Resource
    private ExecutorService executorService;

    @Value("${spring.servlet.multipart.max-file-size}")
    private DataSize maxFileSize;

    /**
     * 根据漫画id删除所有章节
     *
     * @param mangaId 漫画id
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delByMangaId(Long mangaId) {
        // 删除熟肉页码
        mangaEpCookedService.delByMangaId(mangaId);
        // 删除生肉页码
        mangaEpRawService.delByMangaId(mangaId);
        // 删除章节
        lambdaUpdate().eq(MangaEp::getMangaId, mangaId).remove();
    }

    @Override
    public byte[] export(ExportTask task) {
        MangaExportReq params = (MangaExportReq) task.getParams();
        Long epId = params.getEpId();
        MangaEp mangaEp = getById(epId);
        ExceptionUtil.isNull(mangaEp, ErrorCodeEnum.SYSTEM_ERROR_B0401);
        Manga manga = mangaService.getById(mangaEp.getMangaId());
        ExceptionUtil.isNull(manga, ErrorCodeEnum.SYSTEM_ERROR_B0400);

        String filename = FileUtil.getName(task.getFileName());

        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            List<String> nameArr = new CopyOnWriteArrayList<>();
            List<InputStream> isArr = new CopyOnWriteArrayList<>();
            // 异步执行
            CompletableFutureUtil.runAsyncAllAndWait(executorService, () -> {
                // 准备生肉页码图片数据
                if (params.isRaw() || params.isBlank()) {
                    List<MangaEpRaw> rawList = mangaEpRawService.lambdaQuery().eq(MangaEpRaw::getEpId, epId).list();
                    // 从minio下载文件，写入zip
                    for (MangaEpRaw raw : rawList) {
                        if (params.isRaw()) {
                            nameArr.add("raw_" + FileUtil.getName(raw.getImageUrl()));
                            isArr.add(minioService.downLoadFileIs(raw.getImageUrl()));
                        }
                        // 添加抠图页码
                        if (params.isBlank() && StrUtil.isNotBlank(raw.getBlankUrl())) {
                            nameArr.add(FileUtil.getName(raw.getBlankUrl()));
                            isArr.add(minioService.downLoadFileIs(raw.getBlankUrl()));
                        }
                    }
                }
            }, () -> {
                // 准备熟肉页码数据
                if (params.isCooked()) {
                    List<MangaEpCooked> cookedList = mangaEpCookedService.lambdaQuery().eq(MangaEpCooked::getEpId, epId).list();
                    // 从minio下载文件，写入zip
                    for (MangaEpCooked cooked : cookedList) {
                        nameArr.add("cooked_" + FileUtil.getName(cooked.getImageUrl()));
                        isArr.add(minioService.downLoadFileIs(cooked.getImageUrl()));
                    }
                }
            }, () -> {
                // 导出标记
                if (params.isMark()) {
                    byte[] markDoc = mangaMarkService.exportMark(epId);
                    String docName = filename + ".docx";
                    nameArr.add(docName);
                    isArr.add(new ByteArrayInputStream(markDoc));
                }
            });
            // 压缩文件
            ZipUtil.zip(bos, nameArr.toArray(new String[]{}), isArr.toArray(new InputStream[]{}));
            return bos.toByteArray();
        } catch (Exception e) {
            log.error("[zip] zip文件制作失败", e);
            ExceptionUtil.throwOut(ErrorCodeEnum.SYSTEM_ERROR_B0411);
            return null;
        }
    }

    /**
     * 导出章节信息
     *
     * @param params   请求参数
     */
    @Override
    public boolean export(MangaExportReq params) {
        UserInfo user = AuthUtil.getCurrentUser();
        Long epId = params.getEpId();
        MangaEp mangaEp = getById(epId);
        ExceptionUtil.isNull(mangaEp, ErrorCodeEnum.SYSTEM_ERROR_B0401);
        ExceptionUtil.assertion(params.isBlank() && !mangaEp.getCutoutStatus().equals(EpCutoutStatus.DONE.getCode()), ErrorCodeEnum.SYSTEM_ERROR_B0412);
        Manga manga = mangaService.getById(mangaEp.getMangaId());
        ExceptionUtil.isNull(manga, ErrorCodeEnum.SYSTEM_ERROR_B0400);

        String filename = manga.getName() + "_" + StrUtil.trimBlank(mangaEp.getName());

        // 预估压缩包大小
        long size = 0;
        if (params.isCooked()) {
            size += mangaEpCookedService.lambdaQuery().eq(MangaEpCooked::getEpId, epId).count() * maxFileSize.toBytes();
        }
        if (params.isRaw()) {
            List<MangaEpRaw> rawList = mangaEpRawService.lambdaQuery().eq(MangaEpRaw::getEpId, epId).list();
            for (MangaEpRaw raw : rawList) {
                size += maxFileSize.toBytes();
                if (params.isBlank() && StrUtil.isNotBlank(raw.getBlankUrl())) {
                    size += DataSize.ofMegabytes(1).toBytes();
                }
            }
        }

        if (params.isBlank()) {
            filename += "_大白框";
        }
        filename += ".zip";

        ExportTask task = ExportTask.builder()
                .topic(ExportTopicEnum.MANGA_ZIP)
                .uid(user.getUid())
                .params(params)
                // 预估压缩包大小
                .evaluateSize(size)
                .fileName(filename)
                .contentType("application/x-download")
                .build();
        // 提交到队列中
        exportManager.submit(task);
        return true;
    }

    /**
     * 导出章节文本
     *
     * @param epId     章节id
     * @param response 响应对象
     */
    @Override
    public void exportText(Long epId, HttpServletResponse response) {
        MangaEp mangaEp = getById(epId);
        ExceptionUtil.isNull(mangaEp, ErrorCodeEnum.SYSTEM_ERROR_B0401);
        Manga manga = mangaService.getById(mangaEp.getMangaId());
        ExceptionUtil.isNull(manga, ErrorCodeEnum.SYSTEM_ERROR_B0400);

        String filename = manga.getName() + "_" + StrUtil.trimBlank(mangaEp.getName()) + ".docx";

        byte[] markDoc = mangaMarkService.exportMark(epId);
        try (ServletOutputStream outputStream = response.getOutputStream()) {
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/x-download");
            response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=".concat(String.valueOf(URLEncoder.encode(filename, StandardCharsets.UTF_8))));
            IOUtils.write(markDoc, outputStream);
        } catch (IOException e) {
            log.error("导出文本失败", e);
            ExceptionUtil.throwOut(ErrorCodeEnum.SYSTEM_ERROR_B0411);
        }
    }
}
